#pragma once

#include <string>
#include <infiniband/verbs.h>


#include "ibv_helper.h"
#include "protocol.h"


//! RDMA网卡设备信息
struct rdma_device {
    struct ibv_context *ib_ctx;    // RDMA网卡设备上下文
    struct ibv_pd      *pd;        // 保护域
    int                ib_port;    // IB 端口
    // RDMA允许单个端口拥有多个地址,
    int                gid_index;  // GID索引
    union ibv_gid      gid;        // RoCE v2
    uint16_t           lid;        // Local ID
    std::string        link_type;  // IB or Ethernet
    ibv_mtu            active_mtu; 


    rdma_device() : ib_ctx(nullptr), pd(nullptr), 
        ib_port(-1), gid_index(-1), lid(-1) {}
};




//! RDMA上下文
struct rdma_context {
    // struct rdma_device *rdma_dev;
    struct ibv_comp_channel *comp_channel;  // IO完成事件通知管道
    struct ibv_cq *cq;                      // 完成队列
    struct ibv_qp *qp;                      // QP
    uint32_t psn;       // 发送队列数据包初始序列号, 创建QP时随机生成

    rdma_context() : comp_channel(nullptr), 
            cq(nullptr), qp(nullptr) {}
};




/**
 *! @brief 打开RDMA网卡设备, 获取设备信息
 * 
 * @param dev_name       设备名称
 * @param ib_port        IB端口
 * @param link_type      IB or Ethernet
 * @param hint_gid_index 提示的GID索引
 * @param rdma_dev       返回的RDMA网卡设备信息，填充rdma_device结构体信息
 * @return int 
 */
int open_rdma_device(std::string dev_name, int ib_port, 
                    std::string link_type, 
                    int hint_gid_index,
                    struct rdma_device *rdma_dev);




/**
 *! @brief 关闭RDMA网卡资源(清理PD和上下文)
 * 
 * @param rdma_dev 
 * @return int 成功返回0，否则返回错误码
 */
int close_rdma_device(struct rdma_device *rdma_dev);




/**
 *! @brief   初始化RDMA上下文(填充自定义rdma_context类型)
 *  创建RDMA通信相关的软件资源, QP, CQ等
 * 
 * @param ctx       返回的RDMA上下文信息
 * @param rdma_dev  RDMA网卡设备信息
 * @return int      成功返回0，否则返回错误码
 */
int init_rdma_context(struct rdma_context *ctx, 
                struct rdma_device *rdma_dev);




/**
 *! @brief 释放RDMA上下文(填充自定义rdma_context类型)
 * 
 * @param ctx 
 * @return int 
 */
int destroy_rdma_context(struct rdma_context *ctx);



/**
 *! @brief Get the rdma conn info object
 * 
 * @param ctx 
 * @param rdma_dev 
 * @return rdma_conn_info_t 
 */
rdma_conn_info_t get_rdma_conn_info(struct rdma_context *ctx, 
                                struct rdma_device *rdma_dev);




/**
*! @brief   QP的状态机
*  
* RESET   QP的初始状态，所有硬件资源已分配但未配置, 不能发送和接收任何数据，
*         所有队列被清空， 创建QP后自动进入该状态, ERR状态恢复后也进入该状态;
* 
* INIT    QP已绑定基本参数，准备连接配置。设置端口绑定和基本访问权限, 准备接收
*         连接参数（如GID、QPN等）, 必须转为RTR状态后才能接收数据;
* 
* RTR     Ready to Receive, QP已配置完整的连接参数，可以接收数据但还不能发送。
*         建立完整的接收路径（包括对端QP信息）配置流控和重传参数;
* 
* RTS     Ready to Send, QP可同时发送和接收数据，是正常工作状态。可处理所有RDMA操作
*        （Send/Write/Read/Atomic）硬件自动维护序列号（PSN）和重传;
* 
* SQD     Send Queue Drain，发送队列排空. 临时状态，用于优雅停止发送队列。停止接受新
*         的发送请求（Send/WRITE等）继续处理已提交的请求直至完成接收队列仍正常工作.用
*         途：动态调整QP参数或连接迁移
*  
* SQE     Send Queue Error，发送队列错误. 发送队列出现错误但接收队列可能仍有效。触发条件:
*         (1)发送超时或重试次数耗尽;(2)手动置位错误状态. 必须重置到RESET状态后重新初始化.
* 
* ERR     QP发生不可恢复错误。触发原因: (1) 网络链路中断; (2)协议序列号错误; (3)硬件故障
*         所有未完成的操作会被冲刷并返回错误完成事件, 必须通过重置恢复
* 
* UNK     动程序无法确定当前状态（通常表示查询失败）
*/

//! QP状态机
// stateDiagram
// [*] --> IBV_QPS_RESET             QP创建后的初始状态
// 当前状态	允许转换的目标状态
// ------------------------
//  RESET	 INIT
//  INIT	 RTR, ERR
//  RTR	     RTS, ERR
//  RTS	     SQD, ERR
//  SQD	     SQE, RTS, ERR
//  SQE	     ERR, RESET
//  ERR	     RESET



/**
 *! @brief 将QP设置状态设置为INIT状态
 * 
 * @param ctx       RDMA上下文
 * @param rdma_dev  RDMA网卡设备
 * @return int      转换成功状态或错误码
 */
int modify_qp_to_init(struct rdma_context *ctx, 
                struct rdma_device *rdma_dev);




/**
 *! @brief 将QP设置状态设置为RTS(Ready to Send)
 * 
 * @param ctx    RDMA上下文
 * @return int   转换成功状态或错误码
 */
int modify_qp_to_rts(struct rdma_context *ctx);





/**
 *! @brief 将QP设置状态设置为RTR(Ready to Receive)
 * 
 * @param ctx         RDMA上下文
 * @param rdma_dev    RDMA网卡设备
 * @param remote_info RDMA连接信息
 * @return int        转换成功状态或错误码
 */
int modify_qp_to_rtr(struct rdma_context *ctx, 
                    struct rdma_device *rdma_dev,
                    rdma_conn_info_t *remote_info);

